Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 6b8c3416883569ad653925853c4013fc0ec4c06a


Parents : d32040b
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-04T23:58:44-05:00

feat(rnode_support): add jnius availability check for RNode interfaces on Android

Changes

2 files changed, 64 insertions(+), 5 deletions(-)


Diff

diff --git a/meshchatx/src/backend/rnode_support.py b/meshchatx/src/backend/rnode_support.py
index e2183246..e047e468 100644
--- a/meshchatx/src/backend/rnode_support.py
+++ b/meshchatx/src/backend/rnode_support.py
@@ -29,6 +29,17 @@ def android_usbserial4a_available() -> bool:
return True
+def android_jnius_available() -> bool:
+ """True when Chaquopy can import jnius (pyjnius)."""
+ if not _is_chaquopy_android():
+ return False
+ try:
+ import jnius # noqa: F401
+ except ImportError:
+ return False
+ return True
+
+
def desktop_serial_stack_available() -> bool:
try:
from serial.tools import list_ports # noqa: F401
@@ -40,7 +51,7 @@ def desktop_serial_stack_available() -> bool:
def rnode_serial_supported() -> bool:
"""Whether RNode serial and ble:// UART ports can be opened on this platform."""
if _is_chaquopy_android():
- return android_usbserial4a_available()
+ return android_usbserial4a_available() and android_jnius_available()
return desktop_serial_stack_available()
@@ -155,15 +166,21 @@ def guard_invalid_rnode_txpower_in_config(config_path: str) -> bool:
def guard_rnode_interfaces_on_android(config_path: str) -> bool:
- """On Android without usbserial4a, disable RNode interfaces to avoid startup crashes."""
+ """On Android without usbserial4a/jnius, disable RNode interfaces to avoid startup crashes."""
if not _is_chaquopy_android():
return False
if rnode_serial_supported():
return False
+ missing = []
+ if not android_usbserial4a_available():
+ missing.append("usbserial4a")
+ if not android_jnius_available():
+ missing.append("jnius")
disabled = disable_rnode_interfaces_in_config(config_path)
if disabled:
logger.warning(
- "RNode interfaces were disabled because usbserial4a is not installed. "
- "Rebuild the Android app with usbserial4a or remove RNode entries from config.",
+ "RNode interfaces were disabled because %s is not installed on this build. "
+ "Rebuild the Android app with the missing module(s) or remove RNode entries from config.",
+ " and ".join(missing) if missing else "a required module",
)
return disabled

diff --git a/tests/backend/test_rnode_support.py b/tests/backend/test_rnode_support.py
index fd0a242b..0fd66e28 100644
--- a/tests/backend/test_rnode_support.py
+++ b/tests/backend/test_rnode_support.py
@@ -17,12 +17,38 @@ def test_guard_disables_rnode_when_usbserial4a_missing(tmp_path, monkeypatch):
)
monkeypatch.setattr(rnode_support, "_is_chaquopy_android", lambda: True)
monkeypatch.setattr(rnode_support, "android_usbserial4a_available", lambda: False)
+ monkeypatch.setattr(rnode_support, "android_jnius_available", lambda: False)
assert rnode_support.guard_rnode_interfaces_on_android(str(config_path)) is True
assert "interface_enabled = false" in config_path.read_text(encoding="utf-8")
-def test_guard_keeps_rnode_when_usbserial4a_available(tmp_path, monkeypatch):
+def test_guard_disables_rnode_when_jnius_missing(tmp_path, monkeypatch):
+ """usbserial4a alone is not enough: RNS's Android RNodeInterface also needs jnius.
+
+ Chaquopy builds that bundle usbserial4a but not jnius still hit
+ RNS.panic() (os._exit) for any RNode port type (serial, tcp://, ble://),
+ since the jnius check runs before the transport is selected.
+ """
+ config_path = tmp_path / "config"
+ config_path.write_text(
+ """[interfaces]
+ [[RNode TCP]]
+ type = RNodeInterface
+ interface_enabled = True
+ port = tcp://192.0.2.1:4242
+""",
+ encoding="utf-8",
+ )
+ monkeypatch.setattr(rnode_support, "_is_chaquopy_android", lambda: True)
+ monkeypatch.setattr(rnode_support, "android_usbserial4a_available", lambda: True)
+ monkeypatch.setattr(rnode_support, "android_jnius_available", lambda: False)
+
+ assert rnode_support.guard_rnode_interfaces_on_android(str(config_path)) is True
+ assert "interface_enabled = false" in config_path.read_text(encoding="utf-8")
+
+
+def test_guard_keeps_rnode_when_usbserial4a_and_jnius_available(tmp_path, monkeypatch):
config_path = tmp_path / "config"
config_path.write_text(
"""[interfaces]
@@ -34,6 +60,7 @@ def test_guard_keeps_rnode_when_usbserial4a_available(tmp_path, monkeypatch):
)
monkeypatch.setattr(rnode_support, "_is_chaquopy_android", lambda: True)
monkeypatch.setattr(rnode_support, "android_usbserial4a_available", lambda: True)
+ monkeypatch.setattr(rnode_support, "android_jnius_available", lambda: True)
assert rnode_support.guard_rnode_interfaces_on_android(str(config_path)) is False
assert "interface_enabled = True" in config_path.read_text(encoding="utf-8")
@@ -44,6 +71,21 @@ def test_rnode_serial_supported_on_desktop(monkeypatch):
assert rnode_support.rnode_serial_supported() is True
+def test_rnode_serial_supported_on_android_requires_both_modules(monkeypatch):
+ monkeypatch.setattr(rnode_support, "_is_chaquopy_android", lambda: True)
+ monkeypatch.setattr(rnode_support, "android_usbserial4a_available", lambda: True)
+ monkeypatch.setattr(rnode_support, "android_jnius_available", lambda: False)
+ assert rnode_support.rnode_serial_supported() is False
+
+ monkeypatch.setattr(rnode_support, "android_usbserial4a_available", lambda: False)
+ monkeypatch.setattr(rnode_support, "android_jnius_available", lambda: True)
+ assert rnode_support.rnode_serial_supported() is False
+
+ monkeypatch.setattr(rnode_support, "android_usbserial4a_available", lambda: True)
+ monkeypatch.setattr(rnode_support, "android_jnius_available", lambda: True)
+ assert rnode_support.rnode_serial_supported() is True
+
+
def test_guard_disables_rnode_with_invalid_txpower(tmp_path):
config_path = tmp_path / "config"
config_path.write_text(


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────